#!/usr/bin/env python
# -*- encoding=utf-8 -*-

import subprocess, shutil, os,stat,random
import sys, traceback, time, re
from httplib import HTTPSConnection
import urllib, urllib2
from json import JSONDecoder
import json, os
import codecs
from datetime import datetime, timedelta
import hashlib
#from subprocess  import *
# 格式msg检查规则变化，从trust对接，状态需为A，对应的rally上需为P
# 模板的写法改为：第3行：[DTS、SCF、NA]  第4行：为SR需求号；
#

reload(sys)
sys.setdefaultencoding('utf8')
GERRIT_BRANCH = os.environ["GERRIT_BRANCH"]

class base_action():
    def __init__(self):
        self.ci_list = "euleros/CI/ci_project euleros/CI/ci_tools euleros/platform/bep-env euleros/platform/securec euleros/tools/CreateImage euleros/tools/custom_build_tool euleros/tools/euleros-certdb euleros/tools/mkeuleros euleros/tools/obs-env euleros/tools/obs_bin euleros/tools/obs_meta euleros/tools/uname-build-check-x86 euleros/tools/uname-build-check-aarch64 euleros/build euleros/output euleros/tools/gcc_secure euleros/tools/euleros_chroot euleros/self_src/userspace/sys-custom-tool euleros/self_src/userspace/isocut euleros/self_src/userspace/kiwi-template-euleros"
        pass
    def execute(self):
        pass

class dts_rally_check(base_action):
    def tikets_check(self, dts_or_num, log_list, project, branch):
        ##针对  euleros/third_party/open_source/kernelspace/linux-4.19-euleros  next_src 自动生成新提交的情况
        if 'dts' in dts_or_num.lower() and 'scf' in dts_or_num.lower():
            return 0
        if 'dts' in dts_or_num.lower():
            print "------- check DTS -------"
            time = self._get_utctime()
            hashcode = self._gen_hashcode(time)
            utctime = time[0:4] + "-" + time[4:6] + "-" + time[6:8] + "T" + time[8:10] + ":" + time[10:12] + ":" + time[12:]
            dts_list = dts_or_num.split()
            for one_dts in dts_list:
                print one_dts
                if 'cve-' in one_dts:
                    continue
                ret,stat,product = self._get_ticket_status(hashcode, utctime,one_dts)
                if ret == True:
                    if 'true' in stat:
                        print "Error No.5: This" + one_dts +"is not available for it has been closed."
                        return 1
                    if (GERRIT_BRANCH == 'next_net' or GERRIT_BRANCH == 'next_docker' or GERRIT_BRANCH == 'next_hack' or GERRIT_BRANCH == 'next_kernel' or GERRIT_BRANCH == 'next_src' or GERRIT_BRANCH == 'next_beijing' or GERRIT_BRANCH == 'next_openeuler') and (product == 'EulerOS Server V200R008C00' or product == 'UVP ARM 3.0.0' or product == 'UVP KVM 3.0.0'):
                        print "Success: dts check over."
                    else:
                        print "Error No.4: Ths DTS [" + dts_or_num + "] does not matches the product [EulerOS Server V200R008C00]"
                        return 1
                else:
                    print "Error No.6: Failed to check." + one_dts +" is not exist. "
                    return 2
        else:
            #cve number skip
            if 'cve-' in dts_or_num.lower():
                return 0
            ###  这里的情况是：docker组的日志检查还是按照之前的规则进行。。。
            if GERRIT_BRANCH != "next_docker" and 'SCF' in dts_or_num:
                print "------- check SCF ------- "
                ret = self.showcase_check(dts_or_num, log_list)
                if 0 != ret:
                    return 1

                # 检查SR.* 状态
                print "------- check SR* status ----------"
                pbi_obj = open("product_pbi.ini")
                pbi_info = pbi_obj.read()
                pbi_euler = pbi_info.split('EulerOS_Server_V200R008C00')[1].split('\n')[0].strip()
                pbi_uvparm = pbi_info.split('UVP_ARM_3.0.0')[1].split('\n')[0].strip()
                pbi_uvpkvm = pbi_info.split('UVP_KVM_3.0.0')[1].split('\n')[0].strip()
                pbi_list = [pbi_euler,pbi_uvparm,pbi_uvpkvm]
                ret_alm = 6
                for pbi in pbi_list:
                    if ret_alm == 0 or ret_alm == 2:
                        break
                    for log in log_list:
                        print 'use pbi: ' + pbi
                        ret_alm = 0 #self._get_alm_state(log, pbi)
                        if 0 == ret_alm or 2 == ret_alm:
                            break
                if 0 != ret_alm:
                    return 1
            else:
                # 检查SR.* 状态
                print "------- check SR* status ----------"
                pbi_obj = open("product_pbi.ini")
                pbi_info = pbi_obj.read()
                pbi_euler = pbi_info.split('EulerOS_Server_V200R008C00')[1].split('\n')[0].strip()
                pbi_uvparm = pbi_info.split('UVP_ARM_3.0.0')[1].split('\n')[0].strip()
                pbi_uvpkvm = pbi_info.split('UVP_KVM_3.0.0')[1].split('\n')[0].strip()
                pbi_list = [pbi_euler,pbi_uvparm,pbi_uvpkvm]
                ret_alm = 6
                for pbi in pbi_list:
                    if ret_alm == 0 or ret_alm == 2:
                        break
                    # 此时SR* 写在commit_detail处
                    print 'use pbi: ' + pbi
                    ret_alm = self._get_alm_state(dts_or_num, pbi)
                    if 0 == ret_alm or 2 == ret_alm:
                        break
                if 0 != ret_alm:
                    return 1
        return 0

    def _get_alm_state(self, dts_or_num, pbi):
        print "------- check ALM-state ------- "
        if 'AR' in dts_or_num or 'SR' in dts_or_num:
            if 'IR' == dts_or_num.split('-')[0] or 'IREQ' in dts_or_num:
                print 'Error No.7: The rally number shoud not be IR.***'
                return 2
            print 'check Business No'
            query_paras = {"url":"https://rnd-alm-2012.huawei.com","nodename":"eulerosgerrit","pbiVersionID":pbi,"queryString":"(Number = "+ dts_or_num + ")"}
            ret,val = self._execute_rest_api("/GeneralRallyAdapter/outer/common/common_queryStory", "GET", query_paras, host='10.158.212.38:8080')#10.21.241.142  #10.158.212.38
            ticket_state1 = self._parserKey('.*?<ScheduleState>(.*?)</ScheduleState>.*?',val)
            if ticket_state1 == '':
                query_paras = {"url":"http://rnd-alm-fn.huawei.com","nodename":"eulerosgerrit","pbiVersionID":pbi,"queryString":"(Number = "+ dts_or_num + ")"}
                ret,val = self._execute_rest_api("/GeneralRallyAdapter/outer/common/common_queryStory", "GET", query_paras, host='10.158.212.38:8080')#10.21.241.142  #10.158.212.38
                if ret == True:
                    print 'the ticket response is ' + val
                ticket_state2 = self._parserKey('.*?<ScheduleState>(.*?)</ScheduleState>.*?',val)
                print 'the ticket state in Rally is [ ' + ticket_state2 + ' ]'
                if ticket_state2 == "Completed" or ticket_state2 == "Accepted":
                    print "Error: The rally number [" + dts_or_num + "] is in state " + ticket_state2 + ", so you cannot commit with it."
                    return 2
                #elif ticket_state2 != "In-Progress":
                    #print "Error: The rally number [" + dts_or_num + "] is in not in state In-Progress, so you cannot commit with it."
                    #return 2
                if ticket_state2 == '':
                    print 'Error: The rally number [' + dts_or_num + '] is not exist. Please confirm it on https://rnd-alm-2012.huawei.com or http://rnd-alm-fn.huawei.com .'
                    return 1
            else:
                print 'the ticket response is ' + val
                print 'the ticket state in Rally is [ ' + ticket_state1 + ' ]'
                if ticket_state1 == "Completed" or ticket_state1 == "Accepted":
                    print "Error: The rally number [" + dts_or_num + "] is in state " + ticket_state1 + ", so you cannot commit with it."
                    return 2
                #elif ticket_state1 != "In-Progress":
                    #print "------------------- Error --------------------------------------------------"
                    #print dts_or_num + " is not in state In-Progress in Rally system, please check."
                    #print "----------------------------------------------------------------------------"
                    #return 2
        else:
            print 'check Rally Number'
            query_paras = {"url":"https://rnd-alm-2012.huawei.com","nodename":"eulerosgerrit","pbiVersionID":pbi,"queryString":"(FormattedID = "+ dts_or_num + ")"}
            ret,val = self._execute_rest_api("/GeneralRallyAdapter/outer/common/common_queryStory", "GET", query_paras, host='10.158.212.38:8080')#10.21.241.142  #10.158.212.38
            ticket_state = self._parserKey('.*?<ScheduleState>(.*?)</ScheduleState>.*?',val)
            require_no = self._parserKey('.*?<Number>(.*?)</Number>.*?',val)
            print 'the ticket_state is [' + ticket_state + ']'
            print 'the ticket_state name is : ' + require_no
            if 'IR' == require_no.split('-')[0]:
                print 'Error No.7: The rally number shoud not be IR.***'
                return 2
            if ticket_state == '':
                print 'Error: The rally number [' + dts_or_num + '] is not exist. Please confirm it on https://rnd-alm-2012.huawei.com or http://rnd-alm-fn.huawei.com .'
                return 1
            if ticket_state == "Completed" or ticket_state == "Accepted":
                print "Error No.9: The rally number [" + dts_or_num + "] is in state " + ticket_state + ", so you cannot commit with it."
                return 2
            else:
                print "Success: rally check over"
        return 0

    def _execute_rest_api(self, url, method, param, host='', headers = None, urlencode = True):
        data = urllib.urlencode(param)
        if (method.upper() == "GET") and (data != ""):
            url = url + "?"+data
        if headers is None:
            headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
        http_conn = HTTPConnection(host,timeout=600000)
        try:
            if urlencode:
                post_data = urllib.urlencode(param)
            else:
                post_data = str(param)
            http_conn.request(method, url, post_data, headers=headers)

            response = http_conn.getresponse()
            response_status = response.status
            response_content = response.read()
            http_conn.close()

            if (response_status  >=300):
                return False,response_content
            else:
                try:
                    rst = JSONDecoder().decode(response_content)
                except ValueError:
                    rst = response_content
                return True,rst
        except:
            print "".join(traceback.format_exception(*sys.exc_info()))
            return False,{}

    def _parserKey(self,key, result):
        xPattern = re.compile(key)
        strRet = xPattern.search(result)
        if (strRet == None):
            return ''
        else:
            return strRet.group(1)

    def _get_ticket_status(self, hashcode, utctime, dtsno, headers = None,urlencode = True):
        print "------- check ticket state ------- "
        host = 'dtsws.huawei.com'
        url = '/net/DTS/dts/ws/DTSInterface.asmx'
        params = '''
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
   <soapenv:Header>
      <tem:ValidateHeader>
         <tem:Hash>%s</tem:Hash>
         <tem:VisitorId>For_EulerOSCI</tem:VisitorId>
         <tem:Method>GetAuditDtsInfoByDTSNo</tem:Method>
         <tem:UtcTime>%s</tem:UtcTime>
      </tem:ValidateHeader>
   </soapenv:Header>
   <soapenv:Body>
      <tem:GetPageListBy>
         <tem:parameters>
           <tem:WsParameter>
               <tem:Name>Fields</tem:Name>
               <tem:Value>sprodcno,iifclose,sproductno</tem:Value>
            </tem:WsParameter>
            <tem:WsParameter>
               <tem:Name>DtsNo</tem:Name>
               <tem:Value>%s</tem:Value>
            </tem:WsParameter>
            </tem:parameters>
      </tem:GetPageListBy>
   </soapenv:Body>
</soapenv:Envelope>
''' % (hashcode, utctime, dtsno)
        print "DTS check query : " + params
        length = len(params)
        if headers is None:
            headers = {'Content-Type': 'text/xml; charset=utf-8', 'Content-Length': length, 'SOAPAction': '"http://tempuri.org/GetPageListBy"'}
        http_conn = HTTPSConnection(host,key_file='dts.key',cert_file='dts.crt',timeout=600000)
        print "params = " + params
        try:
            urlencode = False
            if urlencode:
                post_data = urllib.urlencode(params)
            else:
                post_data = str(params)
            http_conn.request("POST", url, post_data, headers=headers)

            response = http_conn.getresponse()
            response_status = response.status
            response_content = response.read()
            print "DTS check query response : " + str(response_content)
            http_conn.close()
            if (response_status  >=300):
                return False,response_content,{}
            else:
                status = response_content.split('<anyType xsi:type="xsd:boolean">')[1].split('</anyType>')[0]
                product = response_content.split('<anyType xsi:type="xsd:string">')[2].split("</anyType>")[0]
                return True,status,product
        except:
            print "".join(traceback.format_exception(*sys.exc_info()))
            return False,{},{}

    def _get_utctime(self):
        now = datetime.now()
        utctime = now - timedelta(hours=8)
        strtime = utctime.strftime("%Y%m%d%H%M%S")
        return strtime

    def _gen_hashcode(self, time):
        account = "For_EulerOSCI"
        password = account
        method = "GetAuditDtsInfoByDTSNo"
        encode_str = account + password  + method + time
        try:
            m = hashlib.md5()
            try:
                m.update(encode_str)
            except:
                print "".join(traceback.format_exception(*sys.exc_info()))
            encrypt_str = m.hexdigest()
        except:
            print "".join(traceback.format_exception(*sys.exc_info()))
        return encrypt_str

    def _errorinfo(self, error_info):
        print "--------------- template ---------------"
        print "Module: modification summary      "
        print "                                  "
        print "commit_type: bugfix/CVE/req/other "
        print "commit_detail:                    "
        print "reason:                           "
        print error_info
        print "Tips: If your local template do not match this, please update your dir:  ./repo/repo"
        return 1

    def msg_check(self, msg, project):
        print "------- check msg ------- "
        summary = msg.split("\n")
        #check revert msg
        list = re.findall('^\s*Revert*',summary[0])
        if 1 == len(list):
            if 5 == len(summary):
                sys.exit(0)

        #check commit msg or cherrypick msg
        if ":" not in summary[0]:
            ret = self._errorinfo("Error: ':' should exist between module and summary")
            return 1
        if "Module" in summary[0] and "modification summary" in summary[0]:
            ret = self._errorinfo("Error No.10: Please change the summary, you shoudn't just use the template")
            return 1
        if summary[0].split(':')[0].strip() == None or summary[0].split(':')[1].strip() == None:
            ret = self._errorinfo("Error No.10: Module or summary should not be None")
            return 1
        if len(summary[0].split(':')[0]) != len(summary[0].split(':')[0].strip()):
            print "***" + summary[0] + "***"
            ret = self._errorinfo("Error No.15: There should be no wirtespace in module")
            return 1

	#need space line before commit_type:
        list = re.findall('\s+$',summary[1])
        if 1 > len(list) and "" != summary[1]:
            ret = self._errorinfo("Error No.11: There must be a space line before 'commit_type:'")
            return 1

        if "commit_type:" in summary[2]:
            commit_type = summary[2].split("commit_type:")[1].split("\n")[0].strip()
            if '' == commit_type:
                ret = self._errorinfo("Error: it is null behind commit_type, this line should be one of bugfix/CVE/req/other ")
                return 1
            print "commit_type: " + commit_type

            list = re.findall('bugfix|CVE|req|other',commit_type)
            if 1 > len(list):
                ret = self._errorinfo("Error: commit_type line should be only one of bugfix,CVE,req or other")
                return 1
        else:
            ret = self._errorinfo("Error: the third line in commit msg should be commit_type:.")
            return 1

        if "commit_detail:" in summary[3]:
            commit_detail = summary[3].split("commit_detail:")[1].split("\n")[0].strip()
            if '' == commit_type:
                ret = self._errorinfo("Error: it is null behind commit_detail, this line should be DTS/SCF/NA/CVE ")
                return 1
            print "commit_detail: " + commit_detail
            if commit_type == "CVE":
                cve_list = commit_detail.split(';')
                cve_patten = re.compile("CVE-\d{4}-\d*")
                dts_patten = re.compile("DTS\d{13}")
                if "CVE" not in commit_detail or "DTS" not in commit_detail:
                    print "error : both CVE and DTS needed in commit_detail,please check"
                    return 1
                for cve in cve_list:
                    if not re.match(cve_patten, cve) and not re.match(dts_patten, cve):
                        print "error : wrong format in CVE or DTS ,please check"
                        return 1
            else:
                # S184088   AR_RTOS_BUILD_CI  DTSxxxxx  SCFxxxx   NA  CVE-xx-xx   -- 6个
                list = re.findall('S[1-9][0-9]|AR*|DTS[0-9]{13}$|SCF*|NA|SR*|AR*',commit_detail)
                if 1 > len(list):
                    ret = self._errorinfo("Error: commit_detail line should match with DTS[0-9]{13}$|S[1-9][0-9]$|SCF*|NA")
                    return 1
        else:
            ret = self._errorinfo("Error: the fourth line in commit msg should be commit_detail:.")
            return 1

        if "reason:" in summary[4]:
            if GERRIT_BRANCH != "next_docker" and ('SR' in commit_detail or 'AR' in commit_detail):
                log_list = summary[4].split("reason:")[1].split("\n")[0].split(",")
                if log_list == None:
                    ret = self._errorinfo("Error: The reason line is null, this line should be SR.* Requirement Number")
                    return 1
                for log in log_list:
                    list = re.findall('^\s*SR.*|^\s*AR.*',log)
                    if 1 > len(list):
                        ret = self._errorinfo("Error: the Requirement Number SR|AR is wrong")
                        print "[SR|AR]: " + log
                        return 1
        else:
            ret = self._errorinfo("Error: The fifth lise in commit msg should be release:, please check.")
            return 1
        return 0

    def showcase_check(self, scf, log_list):
        token = 'X-Authorization-Token'
        key = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2NvdW50IjoieDAwNDczNjg0IiwiZXhwIjoxMDE5MTg3NDI0NX0.jLMvVWVYMcBBXj57uJWRjTNhHJPGtPgsaHigsjc8h9A'
        cmd = '''
    curl -X GET --header \"Accept: application/json\" --header \"%s:%s \" \"http://trust.rnd.huawei.com/api/showcase/iteration?number=%s\"
        ''' % (token, key, scf)
        print "cmd = " + str(cmd)
        ret,out,err = shell_cmd(cmd)
        if ret != 0:
            print "get trust data error"
            sys.exit(1)
        out = json.loads(str(out))
        trust_list = []
        trust_list_status = []
        for item in out["completeSR"]:
            trust_list.append(item["requirementNo"].encode())
        for tmp  in out["completeSR"]:
            trust_list_status.append(tmp["progressStatus"].encode())

        for item in out["ar"]:
            trust_list.append(item["requirementNo"].encode())
        for tmp in  out["ar"]:
            trust_list_status.append(tmp["progressStatus"].encode())

        dict_SR = dict(zip(trust_list,trust_list_status))

        for sr in log_list:
            if sr in trust_list:
                print "SCF:   " + sr + " is in trust"
                if "A" == dict_SR[sr]:
                    print sr + " status is A(Accepted), ok!"
                else:
                    print "------------------- Error -----------------------------------"
                    print sr + " status is not A(Accepted) in trust system, please check!"
                    print "refer http://trust.rnd.huawei.com/showcase "
                    print "-------------------------------------------------------------"
                    return 1
            else:
                print "-------------------------------------------"
                print "SCF:" + sr + " is not in trust system, please check!"
                print "refer http://trust.rnd.huawei.com/showcase "
                print "-------------------------------------------"
                return 1
        if ret != 0:
            return 1

        return 0

    def execute(self, commit_msg, project, branch):
        ret = self.msg_check(commit_msg, project)
        if 0 != ret:
            return 1

        summary = commit_msg.split("\n")
        dts_ar = summary[3].split("commit_detail:")[1].split("\n")[0].strip()
        log_list = summary[4].split("reason:")[1].split("\n")[0].strip().split(";")
        print "多个需求号请用;隔开！！！！！！！"
        if 'AR_RTOS_BUILD_CI' == dts_ar or 'S184088' == dts_ar:
            if project not in self.ci_list:
                print "Ticket [ AR_RTOS_BUILD_CI or S184088 ] is only for CI to update, you cannot use!"
                return 1
            else:
                print "modify CI script"
                sys.exit(0)
        try:
            print 'the time is ' + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) + ',start to check.'
            ret = self.tikets_check(dts_ar, log_list, project, branch)
            if 2 == ret:
                return ret
            elif 0 == ret:
                print 'the time is ' + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) + ',end check.'
            else:
                return 1
        except:
            print "".join(traceback.format_exception(*sys.exc_info()))

        return 0

#public functions
def shell_cmd(s_cmd_line, inmsg=None, wait_res = True,ignore = False,time_out = None):
    p= subprocess.Popen(s_cmd_line, stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
    if inmsg:
        p.stdin.write(inmsg)
    if wait_res == False:
        return 0,'',''
    if time_out is not None:
        kill = lambda process: process.kill()
        my_timer = Timer(time_out, kill, [p])
        try:
            my_timer.start()
            out,err=p.communicate()
        finally:
            my_timer.cancel()
    else:
        out,err=p.communicate()
    new_out = ''
    new_err = ''
    try:
        new_out = out.decode('gb2312')
    except:
        try:
            new_out = out.decode('utf8')
        except:
            pass
    try:
        new_err = err.decode('gb2312')
    except:
        try:
            new_err = err.decode('utf8')
        except:
            pass
    if p.returncode != 0 and ignore == False:
        print "exec cmd fail,the cmdline is " + s_cmd_line + ",returncode is " + p.returncode + ",err is " + err + "out is " + out
    return p.returncode,out,err

def msg_rule():
    print "------------ rule ---------"
    print "1. DTS的单号，在DTS网址上查询DTS的状态，不能为已关闭状态；"
    print "2. SCF的单号，需要查询trust系统中的需求号状态，且状态需为A(Accepted)；"
    print "   SCF的单号，要把需求号全列在reason后面，在Rally网址上，需求号的状态为P(In-Progress)；"
    print "3. CI人员的写法与之前一致：S184088 "

def msg_templet():
    print "------------ template ---------"
    print "Module: modification summary -- 不变    "
    print "                             -- 不变    "
    print "commit_type:       -- bugfix/CVE/req/other"
    print "commit_detail:     -- CVE情况必须同时填写CVE和DTS单号并用;隔开。其他情况填写DTS（多个用;隔开）或者SCF号"
    print "reason:    -- 填写关联到的所有SR需求号  "
    print "           -- 填写理由                  "

if __name__ == "__main__":
    msg_rule()
    msg_templet()
    query_obj = open('msg.log')
    query_result = query_obj.read()
    query_obj.close()
    if "project" not in query_result:
        print "Get commit msg failed!"
        sys.exit(1)
    project = query_result.split("project: ")[1].split("\n")[0].replace(" ","").replace("\r","")
    ID = query_result.split("number: ")[1].split("\n")[0].replace(" ","").replace("\r","")
    branch = query_result.split("branch: ")[1].split("\n")[0].replace(" ","").replace("\r","")
    commit_msg=query_result.split("commitMessage: ")[1].split("Change-Id")[0]

    check_obj = dts_rally_check()
    for i in range(1,3):
        ret = check_obj.execute(commit_msg, project, branch)
        if ret != 2:
            break
        else:
            print "**************Retry************* :" + str(i)
            time.sleep(300)
            ret = check_obj.execute(commit_msg, project, branch)
    if ret != 0:
        print "\n"
        sys.exit(ret)
